Add groups#196
Conversation
- Updated package version to 2026.05.28.1200. - Introduced new routes for managing groups, including group creation, editing, and details pages. - Enhanced the Navbar to include a link for groups management. - Modified PlanTagSearchInput to conditionally hide the label. - Replaced error handling in Tags component to utilize a centralized API error message function.
- Added PlanAudioDTO and PlanAudioListResponse interfaces for audio data handling. - Implemented fetchPlanAudioList function to retrieve audio files associated with plans. - Introduced attachDayAudio function to link audio files to specific days. - Updated TaskForm to include plan title in DayAudioSection. - Integrated PlanAudioSearchInput component for searching existing audio files in DayAudioSection. - Improved user instructions for audio uploads and management in the UI.
…sk management - Added new API functions for creating multiple days and bulk deleting days. - Updated the SideBar component to support day selection mode for bulk actions. - Enhanced the DayDeleteDialog to handle bulk deletion. - Refactored usePlanMutations to integrate new API functions for creating and deleting days. - Improved user experience with feedback messages for successful and failed operations.
Confidence Score: 3/5The bulk-delete path in the sidebar has a real regression that can wipe all days from a plan. The bulk day-delete handler does not enforce a minimum number of surviving days. A user with a two-day plan can enter select mode, choose both days, confirm, and delete the entire plan's day list — behavior the old guard was specifically designed to prevent. This is a functional regression on the plan editing path. src/components/routes/task/components/sidebar-component/SideBar.tsx — the bulk delete confirmation and the delete button disable logic need a guard to keep at least one day. Reviews (2): Last reviewed commit: "feat(plan-audio): place plan filter besi..." | Re-trigger Greptile |
| export const SOCIAL_PLATFORMS = [ | ||
| { value: "facebook", label: "Facebook", icon: "Facebook" }, | ||
| { value: "SignIn", label: "SignIn", icon: "SignIn" }, | ||
| { value: "x.com", label: "X (Twitter)", icon: "Twitter" }, |
There was a problem hiding this comment.
Erroneous "SignIn" social platform entry
"SignIn" is not a social platform and appears to be an accidental addition. It will show up in the platform dropdown in GroupSocialLinksEditor as a real option for every group's social links, producing malformed data.
| export const SOCIAL_PLATFORMS = [ | |
| { value: "facebook", label: "Facebook", icon: "Facebook" }, | |
| { value: "SignIn", label: "SignIn", icon: "SignIn" }, | |
| { value: "x.com", label: "X (Twitter)", icon: "Twitter" }, | |
| export const SOCIAL_PLATFORMS = [ | |
| { value: "facebook", label: "Facebook", icon: "Facebook" }, | |
| { value: "x.com", label: "X (Twitter)", icon: "Twitter"}, |
| render={({ field }) => ( | ||
| <Pecha.FormItem className="flex items-center gap-3"> | ||
| <Pecha.FormControl> | ||
| <Pecha.Checkbox | ||
| checked={field.value} | ||
| onCheckedChange={(checked) => | ||
| field.onChange(checked === true) | ||
| } | ||
| /> | ||
| </Pecha.FormControl> | ||
| <Pecha.FormLabel className="text-sm font-bold !mt-0"> | ||
| Public group | ||
| </Pecha.FormLabel> | ||
| </Pecha.FormItem> | ||
| )} | ||
| /> | ||
| <div className="space-y-4"> | ||
| {addedLanguages.map((code) => ( | ||
| <div | ||
| key={code} | ||
| className="relative rounded-lg border border-input bg-[#FAFAFA] dark:bg-[#262626] p-4 space-y-3" | ||
| > | ||
| <button | ||
| type="button" | ||
| onClick={() => removeLanguage(code)} | ||
| className="absolute top-2 right-2 text-muted-foreground hover:text-foreground p-1" | ||
| aria-label={`Remove ${languageLabelForCode(code)}`} | ||
| > | ||
| <IoMdClose className="h-4 w-4" /> | ||
| </button> | ||
| <Pecha.FormField | ||
| control={form.control} | ||
| name={`languages.${code}.title`} | ||
| render={({ field }) => ( | ||
| <Pecha.FormItem> | ||
| <Pecha.FormLabel className="text-sm font-bold"> | ||
| {languageLabelForCode(code)} title | ||
| </Pecha.FormLabel> |
There was a problem hiding this comment.
Plan titles show as IDs due to hydration race condition
The hydration effect guards on hydratedRef.current === groupData.id to avoid running twice, but planOptions is an async query that may arrive after groupData. When that happens, the effect runs with planOptions = [], sets hydratedRef, and when planOptions finally populates (triggering the effect again), the hydratedRef guard returns early — leaving selectedPlans filled with raw IDs as titles via the titleById.get(id) ?? id fallback.
The series_ids hydration correctly avoids this by using a separate useEffect that fetches titles independently; the plan_ids side needs the same treatment.
|
|
||
| setUploading(true); | ||
| try { | ||
| const { image, key } = await uploadImageToS3(file, groupId ?? ""); |
There was a problem hiding this comment.
Image upload passes empty string as
groupId for new groups
When isNew is true, groupId is undefined, so uploadImageToS3(file, groupId ?? "") sends "" to the upload API. If the server uses this parameter for path/ownership resolution, the upload may be rejected or filed under an unowned S3 prefix. The image upload dialogs are rendered unconditionally so a user can trigger this code path before creating the group.
| const { image, key } = await uploadImageToS3(file, groupId ?? ""); | |
| if (!groupId) { | |
| toast.error("Save the group first before uploading images"); | |
| return; | |
| } | |
| const { image, key } = await uploadImageToS3(file, groupId); |
- lay out plan picker and audio library search on one row in day audio UI - preserve search, infinite scroll, preview, and attach behavior - apply formatting-only updates across groups routes and day create dialog
| }; | ||
|
|
||
| const toggleDaySelection = (day_id: string) => { | ||
| setSelectedDayIds((prev) => { | ||
| const next = new Set(prev); | ||
| if (next.has(day_id)) { | ||
| next.delete(day_id); | ||
| } else { | ||
| next.add(day_id); | ||
| } | ||
| return next; | ||
| }); | ||
| }; | ||
|
|
||
| const exitSelectMode = () => { |
There was a problem hiding this comment.
Bulk delete can remove all days from a plan
The single-day delete path was guarded by isEditable && currentPlan?.days.length > 1, ensuring at least one day always remains. The new bulk delete path only requires displayDays.length > 1 to enter select mode, but once inside, all days can be selected and deleted together. A plan with 2 days allows entering select mode, selecting both, and wiping out every day — leaving the plan in a zero-day state that may be invalid server-side or unrecoverable through the UI.
No description provided.